import Subject from "./subject";
import { Fail, Fianlly, Generator, Success } from "../compose";
import { isSubject, Key } from "../utils";
import type {
  CallBack,
  Expense,
  FailComposer,
  FianllyComposer,
  GeneratorComposer,
  ReactiverLike,
  SuccessComposer,
  AsyncHandle,
  PromiseParamsObj,
  PromiseParams
} from "../types";

export default class Reactiver extends Subject {
  generator: GeneratorComposer = new Generator(this);

  private _success: SuccessComposer = new Success(this);

  private _fail: FailComposer = new Fail(this);

  private _fianlly: FianllyComposer = new Fianlly(this);

  constructor(val: Expense, suffix?: PromiseParams) {
    super(isSubject(val) ? val : null, suffix?.key);
   
    this.bindClose = !!suffix?.bindClose;
    if (!isSubject(val)) {
      this.addHandle(val, suffix);
    }
    setTimeout(() => {
      this.run();
    });
  }

  protected async run(
    direct: boolean = false,
    superioerr: Reactiver | undefined = undefined
  ) {
    if (super.proof(direct)) {
      for (let bind of (await this.start(this, superioerr)).binds) {
        if (this.error && this.bindClose) {
          superioerr = this;
          
        }
        await bind.run(true, superioerr);
      }
    }
  }

  private async start(
    reactiver: ReactiverLike,
    superioerr: Reactiver | undefined = undefined
  ) {
    let result;

    try {
      if (superioerr) {
        throw new Error(
          `Superior Reactiver Closed , Name: ${Key.findKey(superioerr.key)}`
        );
      }
      result = await reactiver.generator.run();
    } catch (error: any) {
      reactiver.error = true;
      reactiver._fail.run(error.message || error);
    }

    !reactiver.error && reactiver._success.run(result);
    reactiver._fianlly.run(result);

    reactiver.closed = true;
    return reactiver;
  }

  prior(val: CallBack, caller?: any) {
    this.generator.setPrior(val, caller);
    return this;
  }
  add(val: Expense, suffix?: PromiseParamsObj) {
    if (isSubject(val)) {
      super.addHanlde(val);
    } else {
      this.addHandle(val, suffix);
    }

    return this;
  }

  private addHandle(val: AsyncHandle, suffix?: PromiseParamsObj) {
    this.generator.set(val, suffix );
  }

  undertake(val: CallBack, caller?: any) {
    this.generator.setUndertake(val, caller);
    return this;
  }

  success(val: CallBack, caller?: any) {
    this._success.set(val, caller);
    return this;
  }

  fail(val: CallBack, caller?: any) {
    this._fail.set(val, caller);
    return this;
  }

  fianlly(val: CallBack, caller?: any) {
    this._fianlly.set(val, caller);
    return this;
  }
}
